/*
 * Copyright (C) 2015 Basil Miller
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.gigamole.library;

import com.gigamole.library.utils.AttrValue;
import ohos.agp.components.*;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;


/**
 * Created by GIGAMOLE on 13.04.2016.
 */
public class ShadowLayout extends DirectionalLayout implements ComponentContainer.ArrangeListener, Component.DrawTask, Component.EstimateSizeListener {

    // Default shadow values
    private final static float DEFAULT_SHADOW_RADIUS = 30.0F;
    private final static float DEFAULT_SHADOW_DISTANCE = 15.0F;
    private final static float DEFAULT_SHADOW_ANGLE = 45.0F;
    private final static int DEFAULT_SHADOW_COLOR = Color.DKGRAY.getValue();

    // Shadow bounds values
    private final static int MAX_ALPHA = 255;
    private final static float MAX_ANGLE = 360.0F;
    private final static float MIN_RADIUS = 0.1F;
    private final static float MIN_ANGLE = 0.0F;
    // Shadow paint
    private final Paint mPaint = new Paint() {
        {
            setAntiAlias(true);
            setDither(true);
            setFilterBitmap(true);
        }
    };
    // Shadow bitmap and canvas
    private PixelMap mBitmap;
    // Detect if shadow is visible
    private boolean mIsShadowed;

    // Shadow variables
    private int mShadowColor;
    private int mShadowAlpha;
    private float mShadowRadius;
    private float mShadowDistance;
    private float mShadowAngle;
    private float mShadowDx;
    private float mShadowDy;
    private int padding;

    /**
     * 构造函数
     *
     * @param context 上下文
     */
    public ShadowLayout(final Context context) {
        this(context, null);
    }

    /**
     * 构造函数
     *
     * @param context 上下文
     * @param attrs   attrs
     */
    public ShadowLayout(final Context context, final AttrSet attrs) {
        this(context, attrs, "");
    }

    /**
     * 构造函数
     *
     * @param context   上下文
     * @param attrs     attrs
     * @param styleName styleName
     */
    public ShadowLayout(final Context context, final AttrSet attrs, final String styleName) {
        super(context, attrs, styleName);
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrs) {
        setEstimateSizeListener(this);
        setArrangeListener(this);
        addDrawTask(this);
        // Retrieve attributes from xml
        setIsShadowed(AttrValue.get(attrs, "sl_shadowed", true));
        setShadowRadius(AttrValue.get(attrs, "sl_shadow_radius", DEFAULT_SHADOW_RADIUS));
        setShadowDistance(AttrValue.get(attrs, "sl_shadow_distance", DEFAULT_SHADOW_DISTANCE));
        setShadowAngle(AttrValue.get(attrs, "sl_shadow_angle", DEFAULT_SHADOW_ANGLE));
        setShadowColor(AttrValue.get(attrs, "sl_shadow_color", DEFAULT_SHADOW_COLOR));
    }

    /**
     * 是否显示阴影
     *
     * @return mIsShadowed
     */
    public boolean isShadowed() {
        return mIsShadowed;
    }

    /**
     * isShadowed
     *
     * @param isShadowed isShadowed
     */
    public void setIsShadowed(final boolean isShadowed) {
        mIsShadowed = isShadowed;
        invalidate();
    }

    /**
     * 获取shadowDistance
     *
     * @return mShadowDistance
     */
    public float getShadowDistance() {
        return mShadowDistance;
    }

    /**
     * shadowDistance
     *
     * @param shadowDistance shadowDistance
     */
    public void setShadowDistance(final float shadowDistance) {
        mShadowDistance = shadowDistance;
        resetShadow();
    }

    /**
     * 获取阴影斜角
     *
     * @return mShadowAngle
     */
    public float getShadowAngle() {
        return mShadowAngle;
    }

    /**
     * 设置阴影斜角
     *
     * @param shadowAngle shadowAngle
     */
    @FloatRange
    public void setShadowAngle(@FloatRange(from = MIN_ANGLE, to = MAX_ANGLE) final float shadowAngle) {
        mShadowAngle = Math.max(MIN_ANGLE, Math.min(shadowAngle, MAX_ANGLE));
        resetShadow();
    }

    /**
     * 获取阴影角度
     *
     * @return mShadowRadius
     */
    public float getShadowRadius() {
        return mShadowRadius;
    }

    /**
     * 阴影角度
     *
     * @param shadowRadius shadowRadius
     */
    public void setShadowRadius(final float shadowRadius) {
        mShadowRadius = Math.max(MIN_RADIUS, shadowRadius);

        // Set blur filter to paint
        mPaint.setMaskFilter(new MaskFilter(mShadowRadius, MaskFilter.Blur.NORMAL));
        resetShadow();
    }

    /**
     * 获取阴影颜色
     *
     * @return mShadowColor
     */
    public int getShadowColor() {
        return mShadowColor;
    }

    /**
     * 阴影颜色
     *
     * @param shadowColor shadowColor
     */
    public void setShadowColor(final int shadowColor) {
        mShadowColor = shadowColor;
        mShadowAlpha = Color.alpha(shadowColor);
        resetShadow();
    }

    /**
     * x偏移量
     *
     * @return mShadowDx
     */
    public float getShadowDx() {
        return mShadowDx;
    }

    /**
     * y偏移量
     *
     * @return mShadowDy
     */
    public float getShadowDy() {
        return mShadowDy;
    }

    // Reset shadow layer
    private void resetShadow() {
        // Detect shadow axis offset
        mShadowDx = (float) ((mShadowDistance) * Math.cos(mShadowAngle / 180.0F * Math.PI));
        mShadowDy = (float) ((mShadowDistance) * Math.sin(mShadowAngle / 180.0F * Math.PI));

        // Set padding for shadow bitmap
        padding = (int) (mShadowDistance + mShadowRadius);
        setPadding(padding, padding, padding, padding);
        postLayout();
    }

    private int adjustShadowAlpha(final boolean adjust) {
        return Color.argb(
                adjust ? MAX_ALPHA : mShadowAlpha, mShadowColor, mShadowColor, mShadowColor);
    }

    @Override
    public void postLayout() {
        // Redraw shadow
        super.postLayout();
    }

    @Override
    public boolean onArrange(int left, int top, int width, int height) {
        return false;
    }


    @Override
    public void onDraw(Component component, Canvas canvas) {
        for (int i1 = 0; i1 < getChildCount(); i1++) {
            Component view = getComponentAt(i1);
            if (view instanceof ShadowLayout) {
                continue;
            }
            view.setClipEnabled(false);
            caculateViewAndRender(view, canvas);
        }
    }

    private void caculateViewAndRender(Component child, Canvas canvas) {
        if (isShadowed()) {
            mPaint.setColor(new Color(mShadowColor));
            if (child instanceof Image) {
                PixelMap pixelMap;
                pixelMap = ((Image) child).getPixelMap();
                PixelMap extractedAlpha = pixelMap.createFromAlpha();
                int left = child.getLeft();
                int top = child.getTop();
                canvas.drawPixelMapHolder(new PixelMapHolder(extractedAlpha),
                        left + mShadowDx + padding, top + mShadowDy + padding, mPaint);
            } else if (child instanceof Text) {
                String mText = "";
                mText = ((Text) child).getText();
                mPaint.setTextSize(((Text) child).getTextSize());
                int pointX = child.getLeft();
                Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
                float dy = (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
                int pointY = (child.getBottom() - child.getTop()) / 2 + child.getTop() + (int) dy;
                canvas.drawText(mPaint, mText, pointX + mShadowDx + padding, pointY + padding + mShadowDy);
            } else {
                PixelMap.InitializationOptions options =
                        new PixelMap.InitializationOptions();
                options.size = new Size(child.getWidth(), child.getHeight());
                options.pixelFormat = PixelFormat.ARGB_8888;
                mBitmap = PixelMap.create(options);
                PixelMap extractedAlpha = mBitmap.createFromAlpha();
                int left = child.getLeft();
                int top = child.getTop();
                canvas.drawPixelMapHolder(new PixelMapHolder(extractedAlpha),
                        left, top, mPaint);
            }
            postLayout();
        }
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int specWidth = EstimateSpec.getSize(widthMeasureSpec);
        int specHeight = EstimateSpec.getSize(heightMeasureSpec);
        setEstimatedSize(specWidth, specHeight);
        return false;
    }
}
